﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace MingYuanYun.Data.Abstractions
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly IDictionary<string, Session> _connections = new Dictionary<string, Session>();
        private readonly IDictionary<string, string> _connectionStrings;
        private readonly Driver _driver;

        private bool _wasDisposed = false;

        public UnitOfWork(IEnumerable<KeyValuePair<string, string>> connectionStrings, Driver driver = Driver.NpgsqlDriver)
        {
            _connectionStrings = connectionStrings.ToDictionary(pair => pair.Key.ToLower(), pair => pair.Value);
            _driver = driver;
        }

        public IDbConnection GetConnection(string connectionStringName)
        {
            connectionStringName = connectionStringName.ToLower();

            if (!_connections.TryGetValue(connectionStringName, out var session))
            {
                var connection = ConnectionProvider.GetConnection(_driver, _connectionStrings[connectionStringName]);

                connection.Open();

               //var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);

                _connections.Add(connectionStringName, session = new Session(connection, null));
            }

            return session.Connection;
        }

        public void Dispose()
        {
            if (_wasDisposed)
                return;

            SaveChanges();

            _wasDisposed = true;
        }

        private void SaveChanges()
        {
            List<Exception> exceptions = null;

            void AppendExcetion(Exception ex)
            {
                if (exceptions == null)
                    exceptions = new List<Exception>();

                exceptions.Add(ex);
            }

            foreach (var item in _connections)
            {
                var session = item.Value;

                try
                {
                    session.Transaction?.Commit();
                }
                catch (Exception ex)
                {
                    AppendExcetion(ex);

                    try
                    {
                        session.Transaction?.Rollback();
                    }
                    catch (Exception rex)
                    {
                        AppendExcetion(rex);
                    }
                }
                finally
                {
                    try
                    {
                        session.Transaction?.Dispose();
                    }
                    catch (Exception ex)
                    {
                        AppendExcetion(ex);
                    }

                    try
                    {
                        session.Connection?.Close();
                    }
                    catch (Exception ex)
                    {
                        AppendExcetion(ex);
                    }
                }
            }

            if (exceptions != null)
                throw new AggregateException("UnitOfWork transaction commit exceptions.", exceptions);

            _connections.Clear();
        }
    }

    internal struct Session
    {
        internal Session(IDbConnection connection, IDbTransaction transaction)
        {
            this.Connection = connection;
            this.Transaction = transaction;
        }

        internal IDbConnection Connection { get; }

        internal IDbTransaction Transaction { get; }
    }
}
